package com.es.interpreter.expression.impl;

import com.es.interpreter.context.InterpretContext;
import com.es.interpreter.context.js.param.AbstractJavaScriptBaseInput;
import com.es.interpreter.context.js.param.AttributeInput;
import com.es.interpreter.context.js.param.EvalInput;
import com.es.interpreter.context.js.param.FunctionInput;
import com.es.interpreter.expression.AbstractExpression;
import lombok.NonNull;

import javax.script.*;
import java.util.*;

/**
 * JavaScript解释实现
 *
 * @author Will
 */
public class JavaScriptExpression extends AbstractExpression {
    /**
     * 引擎名称
     */
    public final static String JAVASCRIPT_ENGINENAME = "javascript";
    /**
     * 脚本执行引擎
     */
    private ScriptEngine javascriptEngine;

    /**
     * 无参构造，供反射使用
     */
    public JavaScriptExpression() {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName(JAVASCRIPT_ENGINENAME);
        if (engine == null) {
            throw new RuntimeException("获取JavaScript解析引擎失败！");
        }

        this.javascriptEngine = engine;
    }

    @Override
    public void interpret(InterpretContext context) throws ScriptException, NoSuchMethodException {
        @NonNull Object input = context.getInput();
        Object ret;

        if (input instanceof String) {
            ret = this.execute((String) input);
        } else if (input instanceof AbstractJavaScriptBaseInput) {
            if (input instanceof EvalInput) {
                EvalInput tInput = (EvalInput) input;
                ret = this.execute(tInput.getScript());
            } else if (input instanceof AttributeInput) {
                AttributeInput tInput = (AttributeInput) input;
                ret = this.execute4Attribute(tInput.getScript(), tInput.getAttributeName());
            } else if (input instanceof FunctionInput) {
                FunctionInput tInput = (FunctionInput) input;
                ret = this.execute4Function(tInput.getScript(), tInput.getMethodName(), tInput.getArgs());
            } else {
                AbstractJavaScriptBaseInput tInput = (AbstractJavaScriptBaseInput) input;
                ret = this.execute(tInput.getScript());
            }
        } else {
            throw new RuntimeException("无法解析参数类型，请检查！当前参数：" + input);
        }

        context.setOutput(ret);
    }

    /**
     * 执行一段JavaScript代码
     *
     * @param script JavaScript的代码
     * @return JavaScript代码运行结果的值
     * @throws ScriptException JavaScript代码运行异常
     */
    private Object execute(String script) throws ScriptException {
        return this.javascriptEngine.eval(script);
    }

    /**
     * 执行脚本函数
     *
     * @param script     脚本
     * @param methodName 函数名
     * @param args       函数参数
     * @return
     * @throws ScriptException
     * @throws NoSuchMethodException
     */
    private Object execute4Function(String script, String methodName, Object... args)
            throws ScriptException, NoSuchMethodException {
        this.javascriptEngine.eval(script);
        if (this.javascriptEngine instanceof Invocable) {
            Invocable in = (Invocable) this.javascriptEngine;
            return in.invokeFunction(methodName, args);
        } else {
            throw new RuntimeException("javascriptEngine 不能被作为Function使用！");
        }
    }

    /**
     * 运行一个JavaScript代码段,并获取指定变量名的值
     *
     * @param script        代码段
     * @param attributeName 已知的变量名
     * @return 指定变量名对应的值
     * @throws ScriptException JavaScript代码运行异常
     */
    private Object execute4Attribute(String script, @NonNull String... attributeName) throws ScriptException {
        this.javascriptEngine.eval(script);

        if (attributeName.length == 1) {
            return this.javascriptEngine.getContext().getAttribute(attributeName[0]);
        } else {
            List<Object> ret = new LinkedList<>();
            for (int i = 0; i < attributeName.length; i++) {
                ret.add(this.javascriptEngine.getContext().getAttribute(attributeName[i]));
            }
            return ret.toArray();
        }
    }

    /**
     * 获取当前语句运行后第一个有值变量的值
     *
     * @param script 代码段
     * @return 第一个有值变量的值
     * @throws ScriptException JavaScript代码运行异常
     */
    private Object execute4FirstAttribute(String script) throws ScriptException {
        /*这里重新获取一个JavaScript解析引擎是为了避免代码中有其他调用工具类的地方的变量干扰
         重新获取后,这个JavaScript解析引擎只执行了这次传入的代码,不会保存其他地方的变量
         全局的解析器中会保存最大200个变量,JavaScript解析引擎本身最大保存100个变量*/
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine scriptEngine = manager.getEngineByName(JAVASCRIPT_ENGINENAME);
        if (scriptEngine == null) {
            throw new RuntimeException("获取JavaScript解析引擎失败");
        }

        scriptEngine.eval(script);
        ScriptContext context = scriptEngine.getContext();
        if (context == null) {
            return null;
        }
        Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
        if (bindings == null) {
            return null;
        }
        Set<Map.Entry<String, Object>> entrySet = bindings.entrySet();
        if (entrySet == null || entrySet.isEmpty()) {
            return null;
        }
        for (Map.Entry<String, Object> entry : entrySet) {
            if (entry.getValue() != null) {
                return entry.getValue();
            }
        }
        return null;
    }
}